/*  This program is free software; you can redistribute it and/or modify it
    under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation; either version 2.1 of the License, or (at
    your option) any later version.
    For more details, see the GNU Lesser General Public License (www.fsf.org
    or the COPYING file somewhere in the package)
 */

#include "Properties.hh"

/*! @file src/OSGi/Properties.cc
    @brief Class OSGi::Properties (non-inline) methods.
    @author @ref Guillaume_Terrissol
    @date 27th December 2009 - 22nd January 2014
    @note This file is distributed under the LGPL license.
    Refer to the file COPYING (or http://www.fsf.org) for more information.
 */

#include <iomanip>
#include <sstream>

namespace OSGi
{
//------------------------------------------------------------------------------
//                             Conversion Functions
//------------------------------------------------------------------------------

    namespace
    {
        /*! Converts a boolean to a string.
            @param pValue Value to convert
            @retval "true"  If @p pValue is true
            @retval "false" Otherwise
            @internal
         */
        inline std::string toString(bool pValue)
        {
            return std::string{pValue ? "true" : "false"};
        }


        /*! Converts an integer to a string.
            @param pValue Value to convert
            @return The converted value
            @internal
         */
        std::string toString(int pValue)
        {
            return std::to_string(pValue);
        }


        /*! Converts a floating point value to a string.
            @param pValue Value to convert
            @return The converted value, with large precision
            @internal
         */
        std::string toString(double pValue)
        {
            return dynamic_cast<std::ostringstream&>(std::ostringstream{} << std::setprecision(24) << pValue).str();
        }


        /*! Converts a string to a numerical value.
            @param pValue String holding a value to convert
            @return The value held by @p pValue
            @internal
         */
        template<typename TT>
        TT fromString(const std::string& pValue);


        template<>
        inline bool fromString(const std::string& pValue)
        {
            return pValue != "false";
        }


        template<>
        inline int fromString(const std::string& pValue)
        {
            return stoi(pValue);
        }


        template<>
        inline double fromString(const std::string& pValue)
        {
            return stod(pValue);
        }


        template<>
        inline std::string fromString(const std::string& pValue)
        {
            return pValue;
        }
    }



//------------------------------------------------------------------------------
//                          KeyNotFound Implementation
//------------------------------------------------------------------------------

    /*! @param pKey Missing key name
     */
    KeyNotFound::KeyNotFound(std::string pKey)
        : Exception{pKey + " not found"}
        , mKey{pKey}
    { }


    /*! Defaulted.
     */
    KeyNotFound::~KeyNotFound() noexcept = default;


    /*! Allows to know the missing key which causes this exception throw.
        @return The missing key name
     */
    const std::string& KeyNotFound::key() const noexcept
    {
        return mKey;
    }


//------------------------------------------------------------------------------
//                           Constructor & Destructor
//------------------------------------------------------------------------------

    /*! Defaulted.
     */
    Properties::Properties() = default;


    /*! Defaulted.
     */
    Properties::~Properties() = default;


//------------------------------------------------------------------------------
//                              Property Definition
//------------------------------------------------------------------------------

    /*! Sets a [new] property.
        @param pKey   Property key
        @param pValue Property value (string)
     */
    void Properties::set(std::string pKey, std::string pValue)
    {
        // Shamelessly overwrittes the previous value.
        mValues[pKey] = pValue;
    }


    /*! Sets a [new] property.
        @param pKey   Property key
        @param pValue Property value (boolean)
     */
    void Properties::set(std::string pKey, bool pValue)
    {
        set(pKey, toString(pValue));
    }


    /*! Sets a [new] property.
        @param pKey   Property key
        @param pValue Property value (integer)
     */
    void Properties::set(std::string pKey, int pValue)
    {
        set(pKey, toString(pValue));
    }


    /*! Sets a [new] property.
        @param pKey   Property key
        @param pValue Property value (real number)
     */
    void Properties::set(std::string pKey, double pValue)
    {
        set(pKey, toString(pValue));
    }


    /*! Sets a [new] property.
        @param pKey   Property key
        @param pValue Property value (C string)
     */
    void Properties::set(std::string pKey, const char* pValue)
    {
        set(pKey, std::string{pValue});
    }


//------------------------------------------------------------------------------
//                              Property Retreiving
//------------------------------------------------------------------------------

    /*! Allows to know whether a property does exist.
        @param pKey Key of the property the presence of which to check
        @return @b true if a a property with key @p pKey is registered, @b
        false otherwise
     */
    bool Properties::has(const std::string& pKey) const
    {
        return (mValues.find(pKey) != end(mValues));
    }


    /*! Allows to retrieve a property value.
        @param pKey Key of the property to retrieve
        @return The value of the property the key of which is @p pKey
        @throw KeyNotFound If there is no property with @p pKey as key
     */
    std::string Properties::get(const std::string& pKey) const
    {
        auto lIter = mValues.find(pKey);
        if (lIter != end(mValues))
        {
            return lIter->second;
        }
        else
        {
            throw KeyNotFound{pKey};
        }
    }


    /*! Allows to retrieve a property value.
        @param pKey Key of the property to retrieve
        @return The boolean value of the property the key of which is @p pKey
        @throw KeyNotFound If there is no property with @p pKey as key
     */
    bool Properties::getBool(const std::string& pKey) const
    {
        return fromString<bool>(get(pKey));
    }


    /*! Allows to retrieve a property value.
        @param pKey Key of the property to retrieve
        @return The integer value of the property the key of which is @p pKey
        @throw KeyNotFound If there is no property with @p pKey as key
     */
    int Properties::getInteger(const std::string& pKey) const
    {
        return fromString<int>(get(pKey));
    }


    /*! Allows to retrieve a property value.
        @param pKey Key of the property to retrieve
        @return The floating value of the property the key of which is @p pKey
        @throw KeyNotFound If there is no property with @p pKey as key
     */
    double Properties::getFloat(const std::string& pKey) const
    {
        return fromString<double>(get(pKey));
    }


//------------------------------------------------------------------------------
//                   Property Retreiving (with Default Value)
//------------------------------------------------------------------------------

    /*! Allows to retrieve a property value.
        @param pKey     Key of the property to retrieve
        @param pDefault Default value to return if there is no property with
        @p pKey as key
        @return The value of the property the key of which is @p pKey, or @p
        pDefault if there isn't such a property
        @note I added this template member function to factorize code, and remove
        the previous algorithm, which used exceptions to handle default values
     */
    template<typename TT>
    TT Properties::retrieve(const std::string& pKey, TT pDefault) const
    {
        auto lIter = mValues.find(pKey);
        if (lIter != end(mValues))
        {
            return fromString<TT>(lIter->second);
        }
        else
        {
            return pDefault;
        }
    }


    /*! Allows to retrieve a property value.
        @param pKey     Key of the property to retrieve
        @param pDefault Default value to return if there is no property with
        @p pKey as key
        @return The value of the property the key of which is @p pKey, or @p
        pDefault if there isn't such a property
     */
    std::string Properties::get(const std::string& pKey, std::string pDefault) const
    {
        return retrieve(pKey, pDefault);
    }


    /*! Allows to retrieve a property value.
        @param pKey     Key of the property to retrieve
        @param pDefault Default value to return if there is no property with
        @p pKey as key
        @return The boolean value of the property the key of which is @p pKey,
        or @p pDefault if there isn't such a property
     */
    bool Properties::getBool(const std::string& pKey, bool pDefault) const
    {
        return retrieve(pKey, pDefault);
    }


    /*! Allows to retrieve a property value.
        @param pKey     Key of the property to retrieve
        @param pDefault Default value to return if there is no property with
        @p pKey as key
        @return The integer value of the property the key of which is @p pKey,
        or @p pDefault if there isn't such a property
     */
    int Properties::getInteger(const std::string& pKey, int pDefault) const
    {
        return retrieve(pKey, pDefault);
    }


    /*! Allows to retrieve a property value.
        @param pKey     Key of the property to retrieve
        @param pDefault Default value to return if there is no property with
        @p pKey as key
        @return The floating value of the property the key of which is @p pKey,
        or @p pDefault if there isn't such a property
     */
    double Properties::getFloat(const std::string& pKey, double pDefault) const
    {
        return retrieve(pKey, pDefault);
    }
}
